package com.snake.excel2java.excel.filter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

 
/**
 * 创建时间：2016年8月30日 下午3:01:12
 * 
 * 思路： 创建一个FilterSet，枚举了0~65535的所有char是否是某个敏感词开头的状态
 * 
 * 判断是否是 敏感词开头 | | 是 不是 获取头节点 OK--下一个字 然后逐级遍历，DFA算法
 * 
 * @author andy
 * @version 2.2
 */
public class WordFilter {
 
	private static final Set<Integer> firstSet = new HashSet<Integer>(); // 存储首字
	private static final Map<Integer, WordNode> nodes = new HashMap<Integer, WordNode>(1024, 1); // 存储节点
	private static final Set<Integer> stopwdSet = new HashSet<Integer>(); // 停顿词
	private static final char SIGN = '*'; // 敏感词过滤替换
 
	public static void main(String[] args) throws Exception {
		
		List<String> sensitiveList = new ArrayList<String>();
		
		List<String> stopWordList = new ArrayList<String>();
		
		sensitiveList.add("日");
		sensitiveList.add("日你");
		sensitiveList.add("日你妹");
		sensitiveList.add("日妹妹");
		sensitiveList.add("fuck");
		sensitiveList.add("fudc");

		stopWordList.add("`~!@#$%^&*/\\' \"");
		
		WordFilter.init(sensitiveList, stopWordList);
		
		System.err.println(WordFilter.doFilter("dydy日dsdd日日你ssfuckddFu Ck ffｆｕｃｋggf!!!u&c ###k hhfffuuuucccckkk "));
	
		System.err.println(WordFilter.doFilter("hhfffuuuucccckkk jj"));
		
		System.err.println(WordFilter.isContains("dydy日dsdd日日你ssfuckddFu Ck ffｆｕｃｋggf!!!u&c ###k hhfffuuuucccckkk "));
		
		System.err.println(WordFilter.isContains("hhfffuuuucccckkk jj"));
	}
 
	public static void init(List<String> sensitiveList, List<String> stopWordList) {
		
		firstSet.clear();
		nodes.clear();
		stopwdSet.clear();
		
		// 获取敏感词
		addSensitiveWord(sensitiveList);
		addStopWord(stopWordList);
	}
 
 
	/**
	 * 增加停顿词
	 * 
	 * @param words
	 */
	public static void addStopWord(final List<String> words) {
		
		stopwdSet.clear();
		
		if (words != null && words.size() > 0) {
			
			char[] chs;
			for (String curr : words) {
				
				chs = curr.toCharArray();
				
				for (char c : chs) 
					stopwdSet.add(charConvert(c));

			}
		
		}
	
	}
 
	/**
	 * 添加DFA节点
	 * @param words
	 */
	public static void addSensitiveWord(final List<String> words) {
		
		firstSet.clear();
		nodes.clear();
		
		if (words != null && words.size() > 0) {
			
			char[] chs;
			int fchar;
			int lastIndex;
			WordNode fnode; // 首字母节点
			
			// abc
			// acd
			for (String curr : words) {
				
				chs = curr.toCharArray();
				fchar = charConvert(chs[0]);
				
				if (!firstSet.contains(fchar)) {// 没有首字定义
					
					firstSet.add(fchar);// 首字标志位 可重复add,反正判断了，不重复了
					
					fnode = new WordNode(fchar, chs.length == 1);
					
					nodes.put(fchar, fnode);
					
				} else {
					
					fnode = nodes.get(fchar);
					
					if (!fnode.isLast() && chs.length == 1)
						fnode.setLast(true);
					
				}
				
				lastIndex = chs.length - 1;
				
				for (int i = 1; i < chs.length; i++) 
					fnode = fnode.addIfNoExist(charConvert(chs[i]), i == lastIndex);

			}
		
		}
	
	}
 
	/**
	 * 过滤判断 将敏感词转化为成屏蔽词
	 * @param src
	 * @return
	 */
	public static final String doFilter(final String src) {
		
		char[] chs = src.toCharArray();
		int length = chs.length;
		int currc;
		int k;
		WordNode node;
		
		for (int i = 0; i < length; i++) {
			
			currc = charConvert(chs[i]);
			
			if (!firstSet.contains(currc)) 
				continue;

			node = nodes.get(currc);// 日 2
			if (node == null)// 其实不会发生，习惯性写上了
				continue;
			
			boolean couldMark = false;
			int markNum = -1;
			
			if (node.isLast()) {// 单字匹配（日）
				
				couldMark = true;
				markNum = 0;
				
			}
			// 继续匹配（日你/日你妹），以长的优先
			// 你-3 妹-4 夫-5
			k = i;
			for (; ++k < length;) {
				
				int temp = charConvert(chs[k]);
				
				if (stopwdSet.contains(temp))
					continue;
				
				if(!node.equal(temp)) {
					
					node = node.querySub(temp);
					if (node == null)// 没有了
						break;

				}
				
				if (node.isLast()) {
					
					couldMark = true;
					markNum = k - i;// 3-2
					
				}
				
			}
			
			if (couldMark) {
				
				for (k = 0; k <= markNum; k++) 
					chs[k + i] = SIGN;

				i = i + markNum;
			}
		
		}
 
		return new String(chs);
	}
	
	/**
	 * 是否包含敏感词
	 * @param src
	 * @return
	 */
	public static final boolean isContains(final String src) {
		
		char[] chs = src.toCharArray();
		int length = chs.length;
		int currc;
		int k;
		WordNode node;
		
		for (int i = 0; i < length; i++) {
			
			currc = charConvert(chs[i]);
			
			if (!firstSet.contains(currc)) 
				continue;

			node = nodes.get(currc);// 日 2
			if (node == null)// 其实不会发生，习惯性写上了
				continue;
			
			boolean couldMark = false;
			
			if (node.isLast()) // 单字匹配（日）
				couldMark = true;

			// 继续匹配（日你/日你妹），以长的优先
			// 你-3 妹-4 夫-5
			k = i;
			for (; ++k < length;) {
				
				int temp = charConvert(chs[k]);
				
				if (stopwdSet.contains(temp))
					continue;
				
				if(!node.equal(temp)) {
					
					node = node.querySub(temp);
					if (node == null)// 没有了
						break;

				}
				
				if (node.isLast()) 
					couldMark = true;
				
			}
			
			if (couldMark) 
				return true;

		}
 
		return false;
	}
 
	/**
	 * 大写转化为小写 全角转化为半角
	 * 
	 * @param src
	 * @return
	 */
	private static int charConvert(char src) {
		
		int r = ToDBC(src);
		
		return (r >= 'A' && r <= 'Z') ? r + 32 : r;
	}
	
    /**
     * 全角转半角
     * @param input String.
     * @return 半角字符串
     */
	private static int ToDBC(char src) {

    	if (src == '\u3000') 
    		src = ' ';
    	else if (src > '\uFF00' && src < '\uFF5F') 
    		src = (char) (src - 65248);

    	return src;
    }
 
}
